#include "MultiLevelScheduler.h"
#include "Network.h"
#include <iostream>
#include <queue>
using std::cout;
using std::endl;

MultiLevelScheduler::MultiLevelScheduler(Network* network, unsigned int q) : OSScheduler(network), q(q), currRunningNodeId(-1), lastLevel(3) {
    for (int i = 0; i < 4; i++) {
        priorityQueue[i] = new queue<int>();
    }
    for (vector<int>::iterator it = network->getNodeIds()->begin(); it != network->getNodeIds()->end(); it++) {
        workingQueue.push(*it);
        tt->setPriority(*it,3);
    }
    currRunningNodeId = workingQueue.front();
    workingQueue.pop();
}

MultiLevelScheduler::~MultiLevelScheduler() {
    for (int i = 0; i < 4; i++) {
        delete priorityQueue[i];
    }
}

void MultiLevelScheduler::doScheduling(bool deliveredMsg) {
    if (m_curThread == NULL || doContextSwitch()) {
        Node* curr = network->getNode(currRunningNodeId);
        notifyThreadChange(curr);
        tt->addQuanta(currRunningNodeId, q);
        curr->setQuanta(q);
    } else {
        //TODO update data?
    }
}

int MultiLevelScheduler::getNextQueue() {
    int i(0);
    for (i = 0; i < 3; i++) {//find next
        if (priorityQueue[i]->empty())
            continue; //start from the highest(0) to lowest(3)
        else {
            break;
        }
    }
    return i;
}

bool MultiLevelScheduler::doContextSwitch() {
    bool res = false;
    tt->updateUsedQuanta(m_curThread->getId(), m_curThread->getQuanta());
    if (currThreadStable()) {
        if (!workingQueue.empty()) {
            int nextPriorityLevel = decideQueueLevel(currRunningNodeId);
            priorityQueue[nextPriorityLevel]->push(currRunningNodeId);
            tt->setPriority(currRunningNodeId,nextPriorityLevel);
            currRunningNodeId = workingQueue.front();
            cout<<"Doing context switch... from "<<m_curThread->getId()<<" to "<<currRunningNodeId<<endl;
            workingQueue.pop();
        } else {
            lastLevel = getNextQueue();
            queue<int>* nextQueue = priorityQueue[lastLevel];
            workingQueue = *nextQueue; //TODO check copy
            while (!nextQueue->empty()) {
                nextQueue->pop();
            }
        }
        res = true;
    }
    return res;
}

int MultiLevelScheduler::decideQueueLevel(int nodeId) {
    unsigned int usedQuanta = tt->getUsedQuanta(nodeId);
    int res(-1);
    if (lastLevel < 3 && usedQuanta != 0 && usedQuanta <= (q / 2)) {
        res = lastLevel + 1;
    } else {
        if (lastLevel > 0 && usedQuanta == q) {
            res = lastLevel - 1;
        } else {
            res = lastLevel;
        }
    }
    return res;
}
void MultiLevelScheduler::childReset() {
    currRunningNodeId=-1;
    lastLevel=3;
    for (int i = 0; i < 4; i++) {
        priorityQueue[i] = new queue<int>();
    }
    for (vector<int>::iterator it = network->getNodeIds()->begin(); it != network->getNodeIds()->end(); it++) {
        workingQueue.push(*it);
        tt->setPriority(*it,3);
    }
    currRunningNodeId = workingQueue.front();
    workingQueue.pop();
}
